﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using HandyControl.Tools.Interop;

namespace HandyControl.Data;

// ReSharper disable once InconsistentNaming
internal class GPStream : InteropValues.IStream
{
    protected Stream DataStream;

    // to support seeking ahead of the stream length...
    long _virtualPosition = -1;

    internal GPStream(Stream stream)
    {
        if (!stream.CanSeek)
        {
            const int readBlock = 256;
            var bytes = new byte[readBlock];
            int readLen;
            var current = 0;
            do
            {
                if (bytes.Length < current + readBlock)
                {
                    var newData = new byte[bytes.Length * 2];
                    Array.Copy(bytes, newData, bytes.Length);
                    bytes = newData;
                }
                readLen = stream.Read(bytes, current, readBlock);
                current += readLen;
            } while (readLen != 0);

            DataStream = new MemoryStream(bytes);
        }
        else
        {
            DataStream = stream;
        }
    }

    private void ActualizeVirtualPosition()
    {
        if (_virtualPosition == -1) return;

        if (_virtualPosition > DataStream.Length)
            DataStream.SetLength(_virtualPosition);

        DataStream.Position = _virtualPosition;

        _virtualPosition = -1;
    }

    public virtual InteropValues.IStream Clone()
    {
        NotImplemented();
        return null;
    }

    public virtual void Commit(int grfCommitFlags)
    {
        DataStream.Flush();
        // Extend the length of the file if needed.
        ActualizeVirtualPosition();
    }

    [
        UIPermission(SecurityAction.Demand, Window = UIPermissionWindow.AllWindows),
        SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)
    ]
    public virtual long CopyTo(InteropValues.IStream pstm, long cb, long[] pcbRead)
    {

        const int bufsize = 4096;
        var buffer = Marshal.AllocHGlobal(bufsize);
        if (buffer == IntPtr.Zero) throw new OutOfMemoryException();
        long written = 0;
        try
        {
            while (written < cb)
            {
                var toRead = bufsize;
                if (written + toRead > cb) toRead = (int) (cb - written);
                var read = Read(buffer, toRead);
                if (read == 0) break;
                if (pstm.Write(buffer, read) != read)
                {
                    throw EFail("Wrote an incorrect number of bytes");
                }
                written += read;
            }
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
        if (pcbRead != null && pcbRead.Length > 0)
        {
            pcbRead[0] = written;
        }

        return written;
    }

    public virtual Stream GetDataStream()
    {
        return DataStream;
    }

    public virtual void LockRegion(long libOffset, long cb, int dwLockType)
    {
    }

    protected static ExternalException EFail(string msg)
    {
        throw new ExternalException(msg, InteropMethods.E_FAIL);
    }

    protected static void NotImplemented()
    {
        throw new ExternalException("NotImplemented");
    }

    public virtual int Read(IntPtr buf, int length)
    {
        var buffer = new byte[length];
        var count = Read(buffer, length);
        Marshal.Copy(buffer, 0, buf, length);
        return count;
    }

    public virtual int Read(byte[] buffer, int length)
    {
        ActualizeVirtualPosition();
        return DataStream.Read(buffer, 0, length);
    }

    public virtual void Revert()
    {
        NotImplemented();
    }

    public virtual long Seek(long offset, int origin)
    {
        var pos = _virtualPosition;
        if (_virtualPosition == -1)
        {
            pos = DataStream.Position;
        }
        var len = DataStream.Length;
        switch (origin)
        {
            case InteropValues.StreamConsts.STREAM_SEEK_SET:
                if (offset <= len)
                {
                    DataStream.Position = offset;
                    _virtualPosition = -1;
                }
                else
                {
                    _virtualPosition = offset;
                }
                break;
            case InteropValues.StreamConsts.STREAM_SEEK_END:
                if (offset <= 0)
                {
                    DataStream.Position = len + offset;
                    _virtualPosition = -1;
                }
                else
                {
                    _virtualPosition = len + offset;
                }
                break;
            case InteropValues.StreamConsts.STREAM_SEEK_CUR:
                if (offset + pos <= len)
                {
                    DataStream.Position = pos + offset;
                    _virtualPosition = -1;
                }
                else
                {
                    _virtualPosition = offset + pos;
                }
                break;
        }
        return _virtualPosition != -1 ? _virtualPosition : DataStream.Position;
    }

    public virtual void SetSize(long value)
    {
        DataStream.SetLength(value);
    }

    public void Stat(IntPtr pstatstg, int grfStatFlag)
    {
        var stats = new STATSTG { cbSize = DataStream.Length };
        Marshal.StructureToPtr(stats, pstatstg, true);
    }

    public virtual void UnlockRegion(long libOffset, long cb, int dwLockType)
    {
    }

    public virtual int Write(IntPtr buf, int length)
    {
        var buffer = new byte[length];
        Marshal.Copy(buf, buffer, 0, length);
        return Write(buffer, length);
    }

    public virtual int Write(byte[] buffer, int length)
    {
        ActualizeVirtualPosition();
        DataStream.Write(buffer, 0, length);
        return length;
    }

    [StructLayout(LayoutKind.Sequential)]
    [SuppressMessage("ReSharper", "InconsistentNaming")]
    public class STATSTG
    {

        [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
        public IntPtr pwcsName = IntPtr.Zero;
        public int type;
        [MarshalAs(UnmanagedType.I8)]
        public long cbSize;
        [MarshalAs(UnmanagedType.I8)]
        public long mtime;
        [MarshalAs(UnmanagedType.I8)]
        public long ctime;
        [MarshalAs(UnmanagedType.I8)]
        public long atime;
        [MarshalAs(UnmanagedType.I4)]
        public int grfMode;
        [MarshalAs(UnmanagedType.I4)]
        public int grfLocksSupported;

        public int clsid_data1;
        [MarshalAs(UnmanagedType.I2)]
        public short clsid_data2;
        [MarshalAs(UnmanagedType.I2)]
        public short clsid_data3;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b0;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b1;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b2;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b3;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b4;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b5;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b6;
        [MarshalAs(UnmanagedType.U1)]
        public byte clsid_b7;
        [MarshalAs(UnmanagedType.I4)]
        public int grfStateBits;
        [MarshalAs(UnmanagedType.I4)]
        public int reserved;
    }
}
